package javapop.framework;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javapop.framework.parser.Char;
import javapop.framework.parser.Literal;
import javapop.framework.parser.Ref;

public class Grammar {
	private Map<String, Parser<?>> refs;
	private Set<String> requiredRefs;
	private List<Parser<?>> skipParsers;
	
	public Grammar() {
		refs = new HashMap<String, Parser<?>>();
		requiredRefs = new HashSet<String>();
		skipParsers = new ArrayList<Parser<?>>();
	}
	
	public void skip(Parser<?> skipParser) {
		skipParsers.add(skipParser);
	}
	
	public <T> Parser<T> fetch(String ref) {
		return (Parser<T>) refs.get(ref);
	}
	
	public void register(String ref, Parser<?> parser) {
		refs.put(ref, parser);
	}
	
	public void setMainParser(Parser<?> parser) {
		refs.put("_main_",parser);
	}
	
	public Parser<?> getMainParser() {
		return refs.get("_main_");
	}
	
	public void checkReferences() {
		Set<String> missing = new HashSet<String>();
		for(String ref : requiredRefs) {
			if(!refs.containsKey(ref)) {
				missing.add(ref);
			}
		}
		if(!missing.isEmpty()) {
			throw new Error("Missing grammar references: "+missing);
		}
	}
	
	protected <T> Parser<T> ref(String ref) {
		requiredRefs.add(ref);
		return (Parser<T>) new Ref<T>(this,ref);
	}
	
	protected Parser<Character> character(Character ch) {
		return new Char(ch);
	}
	
	protected Parser<String> literal(String lit) {
		return new Literal(lit);
	}

	@SuppressWarnings("unchecked")
	public <T> ParseResult<T> parse(ParseInput input) {
		DefaultParseContext ctx = new DefaultParseContext();
		for(Parser<?> skipParser : skipParsers) {
			ctx.skip(skipParser);
		}
		return (ParseResult<T>) getMainParser().parse(ctx, input);
	}

	@SuppressWarnings("unchecked")
	public <T> ParseResult<T> parse(ParseContext<?> ctx, ParseInput input) {
		for(Parser<?> skipParser : skipParsers) {
			ctx.skip(skipParser);
		}
		// Hakim modif
		ParseResult<?> res = (ParseResult<T>) getMainParser().parse(ctx, input);
		if(res.isError())
			return (ParseResult<T>)ctx.deepest();
		else
			return (ParseResult<T>)res;
	}

	@Override
	public String toString() {
		StringBuffer buf = new StringBuffer();
		buf.append("<_main_> ::= ");
		buf.append(refs.get("_main_"));
		buf.append("\n");
		TreeSet<String> sortedRefs = new TreeSet<String>(refs.keySet());
		for(String ref : sortedRefs) {
			if(!ref.equals("_main_")) {
				buf.append("<");
				buf.append(ref);
				buf.append("> ::= ");
				buf.append(refs.get(ref));
				buf.append("\n");
			}
		}
		return buf.toString();
	}
}
